{---------------------------------------------------------------------

Title: General Support Routines

File: SIPSXM-GSR

Author: R.D.Villwock aka 'Big Bob'

First Written: September 18, 2006

Current Version: 1.51

Last Modified: May 21, 2007

----------------------------------------------------------------------}
{ ---------------- Data Constructor Functions ---------------- }
function on_init_CCMenus
  declare ui_menu CC_Menu  { Std CC selection Menu }

    BuildCCMenu(CC_Menu,0)

    move_control(CC_Menu,0,0)  { Hide until opened }

  declare ui_menu CCP_Menu { CC selection Menu with PW }

    BuildCCMenu(CCP_Menu,1)

    move_control(CCP_Menu,0,0) { Hide until opened }
end function { on_init_CCMenus }

function on_init_GlobalArray 
   { --------------------- Global Array Offsets --------------------- }  

  declare const G_Low_Key := 1     { Lowest note playable by Instrument 0..127 }

  declare const G_Top_Key := 2     { Highest playable note 0..127 }

  declare const G_Bending_On := 3  { SLS may be using tuning function, data-less }

  declare const G_Bending_Off := 4 { SLS is no longer using tuning, data-less }

  declare const G_MSC := 5         { Buffered Member Script Code, not broadcast }

  declare const G_PFC := 6         { Buffered User Preset Format Code, not broadcast }

  declare const G_PIB_MSC := 96    { Preset Import Block, sending Member Script Code }

  declare const G_PIB_PFC := 97    { Preset Import Block, sending Preset Format Code }

  declare const G_PIB_PX  := 98    { Preset Import Block, User Preset Index }

  declare const G_PIB_Name := 99   { Preset Import Block, Packed Name, words 0..2 }

  declare const G_PIB_UP0  := 102  { Preset Import Block, User Preset 1st Parameter }

  declare const G_PIB_UP24 := 126  { Preset Import Block, User Preset last Parameter }

  declare const G_PIB_Tag := 127   { Preset Import Block, duplicate of Preset Index }
    { -------- Global Data Array and Initial Values for Suite -------- }

  declare Global[128]              { Global Data array for Suite }

    Global[0] := 0                 { Offset 0 is used for index/sentinel }

    Global[G_Low_Key] := 2         { Default value for Low_Key, D-2 }

    Global[G_Top_Key] := 127       { Default value for Top_Key, G8 }
    Global[G_MSC] := MSC           { Default or self-value of persistent MSC }

    Global[G_PFC] := PFC           { Default or self-value of persistent PFC } 

    _read_persistent_var(Global)   { Prior MSC/PFC, etc if not fresh install }        
end function { on_init_GlobalArray }

function on_init_ScaleTones

{ Allocate and initialize Tones array with Key Name prefixes }

  declare !Tones[12]   { Allocate Scale Tones Array }

    Tones[0] := 'C'

    Tones[1] := 'C#' 

    Tones[2] := 'D' 

    Tones[3] := 'D#' 

    Tones[4] := 'E' 

    Tones[5] := 'F' 

    Tones[6] := 'F#' 

    Tones[7] := 'G' 

    Tones[8] := 'G#' 

    Tones[9] := 'A' 

    Tones[10] := 'A#' 

    Tones[11] := 'B'

end function { on_init_ScaleTones }


{ ------------------- General Support Routines ------------------ }
function AddSubPanel(pmap,smap,w,x,y)

  { pmap = Current, full cmap array[36]

    smap = Sub-Panel array[w*h] where w*h <= 36

       w = Width of equivalent smap[w,h]

     x,y = pmap anchor point (for upper-left corner of sub-panel)

    This routine modifies current pmap to contain smap as a popup }

  declare ap

  declare np

  declare ns

  ap := x + 6*y - 7    { Linear Anchor Point offset }

  ns := 0        { Start index of smap }

  np := ap       { Start index of pmap anchor point }

  while np < 36  { smap[ns] lies within pmap }

    pmap[np] := smap[ns]    { Copy UI Control Index }

    inc(ns)                 { Next smap position }

    if ns < num_elements(smap)  { smap has more elements }

      { Now compute corresponding pmap index, np }

      np := ns/w*6 + ns mod w + ap

    else { smap exhausted }

      np := 36  { Force loop exit }

    end if

  end while 

end function { AddSubPanel }

{ Open Menu and assign selection to CC }
function BtnMenu(pf,menu) { pf = parameter family }
  if CC_Pending < 0    { Cancellation pending }
    pf.Btn := pf.state { Ignore this request }
    exit
  end if
  if CC_Pending > 0    { 1st level is still open }
    CC_Pending := -1   { Cancel its pending CC entry }
    wait(2000)   { Wait for cancellation to complete } 
  end if
  CC_Pending := 1      { New 1st level request starting }

  move_control(pf.Btn,0,0)  { Replace button with menu }
  move_control(menu,pf.x,pf.y)

  menu := 0    { Display menu header prior to drop-down }

  while CC_Pending > 0 

    wait(1000) { Wait for CC selection or cancellation }

  end while

  move_control(pf.Btn,pf.x,pf.y)

  move_control(menu,0,0)  { Replace menu with Button }
  if CC_Pending = 0       { CC selection was made }

    pf.cc := menu         { Update assigned CC# }
  else if CC_Pending = -2 { 'Outside' cancellation }
    CC_Pending := 0       { Resume idle state }    
  end if
  SetBtn(pf)  { Update button display of assigned CC }
end function { BtnMenu }

function BuildCCMenu(cc_menu,type)

  { type = 0, No Pitch Wheel, type = 1, include Pitch Wheel }

  declare n



  add_menu_item(cc_menu,'  - CC Menu -',0)
  add_menu_item(cc_menu,'  ----------------------',0)   

  add_menu_item(cc_menu,'  MIDI CC - OFF -',0)

  add_menu_item(cc_menu,'  Last CC Moved',-1)

  add_menu_item(cc_menu,'  ----------------------',0)    

  if type # 0 { Add pitch wheel to menu }

    add_menu_item(cc_menu,'     Pitch Wheel',128)

  end if 

  for n := 1 to TopCC

    add_menu_item(cc_menu,'CC ' & n & ' ' & CC_Name[n],n)

  end for

end function { BuildCCMenu }



function Change_Range(pf)  { pf = parameter family }

{ Update Knob setting according to any Range change

  and then update all related 'family' parameters }

  if (pf.CC # 0)

    Update_Knob(pf) { Update knob for new range } 

  end if

end function { Change_Range }

function CopyArray(a1,a2)
  { Copy array a2 to array a1 }
  declare i
  
  for i := 0 to num_elements(a1) - 1
    a1[i] := a2[i]
  end for
end function { CopyArray }



function Do_Preset

{ Process Preset Menu commands and indices }

  declare upx

  

  message('')       { Clear any prior status message }

  upx := Preset - User0     { User Preset Index, (if Preset >= User0) }

  if Preset < 0

    Command := Preset       { This is a 'command' index }

    ImpActv := false        { Reset for next import }

  else   { This is a preset index }

    if Preset < User0       { It's a Built_In preset }

      Command := No_Cmd     { Cancel any pending commands }

    else 

      if Command = Save_As  { User preset selected with Save As pending }

        Save_Preset(upx)    { Save current parms to the specified User Preset }

        Command := No_Cmd   { Cancel the Save_As command }      

        exit                { We're done }

      else if Command = Import

        Command := No_Cmd   { Cancel Import }

      end if

    end if

    if ((upx >= 0) and (UserParms[upx*MaxParms + UP_Empty] = 0))

      Preset := No_Cmd

      message('Preset Empty')

    else  { Selected preset is not empty }
      Call(FetchPreset)

      if (Command = Export)

        Do_Export(upx)      { Send Preset upx to Importing script }

      end if

    end if

    if Command = Rename     { If rename is still active (User was selected) }

      Update_Name(upx)      { Update selected user preset name }

      Command := No_Cmd     { Cancel rename command }

    else if Command = Export

      Preset := Export      { Continue to display 'Export' }

    end if

  end if

  Do_Edit   { Check for Rename command }

end function { Do_Preset }



function LoadParm(control,offset)

{ Update Control unless Parm is the undefined token }

  if PresetBfr[offset] # NoParm

    control := PresetBfr[offset]

  end if

end function { LoadParm }



function NOP

{ Dummy action to avoid empty conditionals for the KSP }

  Global[0] := Global[0]

end function { NOP }

function Note2Key(midi_note,key_name) 

{ Converts a MIDI note# to a Scale-tone/Octave format string } 
  key_name := Tones[midi_note mod 12] & (midi_note/12 -2) 

end function { Note2Key }

function SetACC(pf) { Assign CC for parameter add-on control }

  BtnMenu(pf,CCP_Menu)   { Use full Menu with PW }

  if (pf.cc = 0)

    pf.knob := pf.iKnob  { Restore last knob image }

    exit                 {  when CC is deassigned  }

  end if  

  Update_Knob(pf)        { Update knob per CC value }   

end function { SetACC }


function SetBtn(pf) { Update Button Caption & Family Parms }

  if pf.CC = 0       { De-assigning CC }

    set_text(pf.Btn,pf.cap & ' -OFF-')

    pf.Btn := 0      { Darken button }

  else { Assigning new CC# }

    if pf.CC = 128   { Assigning Pitch Wheel }

      set_text(pf.Btn,pf.cap & ' = PW')

    else  { Assigning Std CC 1..119 }

      set_text(pf.Btn,pf.cap & '# = ' & pf.CC)

    end if

    pf.Btn := 1      { Light button }

  end if

  pf.state := pf.Btn { Update 'internal' state }

end function { SetBtn }


function SetCC(pf) { Assign family CC for standard control }

  BtnMenu(pf,CC_Menu)  { Use Std Menu (no PW) }

end function { SetCC }



function Set_Knob(pf) { pf = parameter family }

{ When Knob is manually changed, all corresponding 'family'

  parms are updated and if icc is non-zero, the corresponding

  add-on CC is initialized to its minimum value of 0 }

  pf.iKnob := pf.Knob  { Update internal Knob Image }

  if pf.CC # 0         { Skip this if CC is not assigned }

    set_controller(pf.CC,0)   { Set CC to min }

  end if

end function { Set_Knob }

function ShowIR 

{ Updates display of IR and LowKey/TopKey from Low_Key/Top_Key } 
  Note2Key(Global[G_Low_Key],LowKey) 

  Note2Key(Global[G_Top_Key],TopKey) 

  set_text(IR_Box,' Low Key: ' & LowKey) { Update display } 

  add_text_line(IR_Box,' High Key: ' & TopKey)  

end function { ShowIR }

function ShowPanel(pmap)

{ Displays a full-sized (6x6) control panel as specified in a Panel Map. pmap is a
  36-element array that specifies the anchor positions of all controls by means of
  their 'handles' (as implictly associated with the controls in the SetXY function).
  All other controls not specified in pmap are hidden. Negative entries in the pmap
  indicate panel positions that have no Control 'anchored' there. Thus SetXY is coded
  to ignore negative handles. Since SetXY is a lengthy function, ShowPanel is written
  to only expand SetXY one time (even though it is looped-through twice at runtime).
  This is done to reduce the overall size of the compiled script. }
  declare i

  declare imax

  declare n
  declare phase

  declare x

  declare y

  if CC_Pending # 0  
    CC_Pending := -2  { Cancel any pending CC assignment }
  end if
  phase := 1  { Use 1st inner-loop pass to hide all controls }
  x := 0
  y := 0
  imax := MaxControl
  while phase < 3
    for i := 0 to imax
      if phase = 2
        n := pmap[i]        { Get control 'handle' }
        x := (i mod 6) + 1  { Determine it's position on panel }
        y := i/6 + 1
      else { phase 1 }
        n := i
      end if
      SetXY(n,x,y)  { Move control 'n' to position x,y }
    end for
    inc(phase)    { Use 2nd inner-loop pass to display all }
    imax := 35    {  the pmap-specified controls (0..35)   }
  end while
end function { ShowPanel }


function Update_Knob(pf)  { pf = parameter family }

{ Reads current value of assigned CC and updates 

  panel Knob and all related 'family' parameters }

  declare Mval   { MIDI CC value }

  

  Mval := CC[pf.CC]      { Read value of assigned MIDI CC }

  if pf.CC = PitchWheel  { Normalize value to 0..127 }

    Mval := (abs(Mval) - 32)/64

  end if

  Mval := ((12700 + Mval*pf.Range)*pf.iKnob + 6350)/12700 { Update Panel Knob }

  if Mval > pf.Kmax    { Clamp value to max for Knob }

    Mval := pf.Kmax

  end if

  pf.Knob := Mval  { Update the Knob or Edit box }

end function { Update_Knob }


function on_init_CC
{ Allocate and initialize 'standard' MIDI CC assignments per MMA } 
  declare @NA  { Not Assigned }
    NA := 'Undefined' 

  declare !CC_Name[TopCC+1]

    CC_Name[0]   := 'Bank Select'

    CC_Name[1]   := 'Mod Wheel'

    CC_Name[2]   := 'Breath Control'

    CC_Name[3]   := NA

    CC_Name[4]   := 'Foot Controller'

    CC_Name[5]   := 'Portamento Time'

    CC_Name[6]   := 'Data Entry MSB'

    CC_Name[7]   := 'Channel Volume'

    CC_Name[8]   := 'Balance'

    CC_Name[9]   := NA

    CC_Name[10]  := 'Pan'

    CC_Name[11]  := 'Expression'

    CC_Name[12]  := 'Efx Ctl 1'

    CC_Name[13]  := 'Efx Ctl 2'

    CC_Name[14]  := NA

    CC_Name[15]  := NA

    CC_Name[16]  := 'General Ctl #1'

    CC_Name[17]  := 'General Ctl #2'

    CC_Name[18]  := 'General Ctl #3'

    CC_Name[19]  := 'General Ctl #4'

    CC_Name[20]  := NA

    CC_Name[21]  := NA

    CC_Name[22]  := NA

    CC_Name[23]  := NA

    CC_Name[24]  := NA

    CC_Name[25]  := NA

    CC_Name[26]  := NA

    CC_Name[27]  := NA

    CC_Name[28]  := NA

    CC_Name[29]  := NA

    CC_Name[30]  := NA

    CC_Name[31]  := NA

    CC_Name[32]  := 'LSB for CC 0'

    CC_Name[33]  := 'LSB for CC 1'

    CC_Name[34]  := 'LSB for CC 2'

    CC_Name[35]  := 'LSB for CC 3'

    CC_Name[36]  := 'LSB for CC 4'

    CC_Name[37]  := 'LSB for CC 5'

    CC_Name[38]  := 'LSB for CC 6'

    CC_Name[39]  := 'LSB for CC 7'

    CC_Name[40]  := 'LSB for CC 8'

    CC_Name[41]  := 'LSB for CC 9'

    CC_Name[42]  := 'LSB for CC 10'

    CC_Name[43]  := 'LSB for CC 11'

    CC_Name[44]  := 'LSB for CC 12'

    CC_Name[45]  := 'LSB for CC 13'

    CC_Name[46]  := 'LSB for CC 14'

    CC_Name[47]  := 'LSB for CC 15'

    CC_Name[48]  := 'LSB for CC 16'

    CC_Name[49]  := 'LSB for CC 17'

    CC_Name[50]  := 'LSB for CC 18'

    CC_Name[51]  := 'LSB for CC 19'

    CC_Name[52]  := 'LSB for CC 20'

    CC_Name[53]  := 'LSB for CC 21'

    CC_Name[54]  := 'LSB for CC 22'

    CC_Name[55]  := 'LSB for CC 23'

    CC_Name[56]  := 'LSB for CC 24'

    CC_Name[57]  := 'LSB for CC 25'

    CC_Name[58]  := 'LSB for CC 26'

    CC_Name[59]  := 'LSB for CC 27'

    CC_Name[60]  := 'LSB for CC 28'

    CC_Name[61]  := 'LSB for CC 29'

    CC_Name[62]  := 'LSB for CC 30'

    CC_Name[63]  := 'LSB for CC 31'

    CC_Name[64]  := 'Sustain On/Off'

    CC_Name[65]  := 'Portamento On/Off'

    CC_Name[66]  := 'Sustenuto On/Off'

    CC_Name[67]  := 'Soft Pedal On/Off'

    CC_Name[68]  := 'Legato Footswitch'

    CC_Name[69]  := 'Hold 2'

    CC_Name[70]  := 'Sound Ctl 1'

    CC_Name[71]  := 'Sound Ctl 2'

    CC_Name[72]  := 'Sound Ctl 3'

    CC_Name[73]  := 'Sound Ctl 4'

    CC_Name[74]  := 'Sound Ctl 5'

    CC_Name[75]  := 'Sound Ctl 6'

    CC_Name[76]  := 'Sound Ctl 7'

    CC_Name[77]  := 'Sound Ctl 8'

    CC_Name[78]  := 'Sound Ctl 9'

    CC_Name[79]  := 'Sound Ctl 10'
    CC_Name[80]  := 'General Ctl #5'

    CC_Name[81]  := 'General Ctl #6'

    CC_Name[82]  := 'General Ctl #7'

    CC_Name[83]  := 'General Ctl #8'

    CC_Name[84]  := 'Portamento Ctl'

    CC_Name[85]  := NA

    CC_Name[86]  := NA

    CC_Name[87]  := NA

    CC_Name[88]  := NA

    CC_Name[89]  := NA
    CC_Name[90]  := NA

    CC_Name[91]  := 'Efx 1 Depth'

    CC_Name[92]  := 'Efx 2 Depth'

    CC_Name[93]  := 'Efx 3 Depth'

    CC_Name[94]  := 'Efx 4 Depth'

    CC_Name[95]  := 'Efx 5 Depth'

    CC_Name[96]  := 'Data Inc'
    CC_Name[97]  := 'Data Dec'

    CC_Name[98]  := 'NRPN LSB'

    CC_Name[99]  := 'NRPN MSB'
    CC_Name[100] := 'RPN LSB'

    CC_Name[101] := 'RPN MSB'

    CC_Name[102] := NA

    CC_Name[103] := NA

    CC_Name[104] := NA

    CC_Name[105] := NA

    CC_Name[106] := NA

    CC_Name[107] := NA

    CC_Name[108] := NA

    CC_Name[109] := NA
    CC_Name[110] := NA

    CC_Name[111] := NA

    CC_Name[112] := NA

    CC_Name[113] := NA

    CC_Name[114] := NA

    CC_Name[115] := NA

    CC_Name[116] := NA

    CC_Name[117] := NA

    CC_Name[118] := NA

    CC_Name[119] := NA

end function { on_init_CC }


